// 为一个变量指定类型的语法是使用"变量: 类型"的形式
let num: number = 123;
// num = "abc"; // error 不能将类型"string"分配给类型'number'

// !布尔类型
let bool: boolean = false;
bool = true;
let bool2: boolean = !!0;
console.log(bool2); //false

// !数值类型
let num2: number;
num = 123;
// num = "123"; // error 不能将类型"123"分配给类型"number"
num = 0b1111011; //  二进制的123
num = 0o173; // 八进制的123
num = 0x7b; // 十六进制的123

// !字符串
let str: string = "Lison";
str = "Li";
const first = "Lison";
const last = "Li";
str = `${first} ${last}`;
console.log(str); // 打印结果为:Lison Li

// !数组
let list1: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3];

// !null和undefined
// 默认情况下 undefined 和 null 可以赋值给任意类型的值，也就是说你可以把 undefined 赋值给 void 类型，也可以赋值给 number 类型
let u: undefined = undefined; // 这里可能会报一个tslint的错误：Unnecessary initialization to 'undefined'，就是不能给一个值赋undefined，但我们知道这是可以的，所以如果你的代码规范想让这种代码合理化，可以配置tslint，将"no-unnecessary-initializer"设为false即可
let n: null = null;

// !object
// object 类型的变量存的是引用
let strInit = "abc";
let strClone = strInit;
strClone = "efg";
console.log(strInit); // 'abc'

let objInit = { a: "aa" };
let objClone = objInit;
console.log(objClone); // {a:"aa"}
objInit.a = "bb";
console.log(objClone); // { a: 'bb' }

// 希望一个变量或者函数的参数的类型是一个对象的时候，使用这个类型
let obj: object;
obj = { name: "Lison" };

// 希望一个值必须是对象而不是数值等类型时，比如我们定义一个函数，参数必须是对象，这个时候就用到object类型了
function getKeys(obj: object) {
  return Object.keys(obj); // 会以列表的形式返回obj中的值
}
getKeys({ a: "a" }); // ['a']

// !symbol
// 表示独一无二的值，通过 Symbol 函数生成
let sym: symbol = Symbol();

// unique symbol
// 是symbols的子类型，这种类型的值只能由Symbol()或Symbol.for()创建，或者通过指定类型来指定一个值是这种类型
// 这种类型的值仅可用于常量的定义和用于属性名。
// 定义unique symbol类型的值，必须用const不能用let
const key1: unique symbol = Symbol();
let key2: symbol = Symbol();
const obj2 = {
  [key1]: "value1",
  [key2]: "value2",
};
console.log(obj2[key1]); //value1
console.log(obj2[key2]); //value2

// !元组
// 元组可以看做是数组的拓展，它表示已知元素数量和类型的数组。确切地说，是已知数组中每一个位置上的元素的类型
// 当我们为 tuple 赋值时：各个位置上的元素类型都要对应，元素个数也要一致
let tuple: [string, number, boolean];
tuple = ["a", 2, false];
// tuple = [2, "a", false]; // error 不能将类型“number”分配给类型“string”。 不能将类型“string”分配给类型“number”。
// tuple = ["a", 2]; // error Property '2' is missing in type '[string, number]' but required in type '[string, number, boolean]'

// 访问元组中元素
tuple[0].split(":"); // right 类型"string"拥有属性"split"
// tuple[1].split(":"); // error 类型“number”上不存在属性“split”

// !枚举
// 给一组数值赋予名字
// 例:定义一组角色，每一个角色用一个数字代表

// 默认编号
enum Roles {
  SUPER_ADMIN,
  ADMIN,
  USER,
}
const superAdmin = Roles.SUPER_ADMIN;
console.log(superAdmin); // 0

// 为每个值都赋予不同的、不按顺序排列的值
enum Roles2 {
  SUPER_ADMIN = 1,
  ADMIN = 3,
  USER = 7,
}
console.log(Roles2[3]); // 'ADMIN'

// !Any
// 不要滥用 any，如果任何值都指定为 any 类型，那么 TypeScript 将失去它的意义
// 如果类型是未知的，更安全的做法是使用unknown类型

// 不能清楚地知道一个值到底是什么类型，这时就需要用到 any 类型，即任意类型
let value: any;
value = 123;
value = "abc";
value = false;

// 定义数组类型时使用 any 来指定数组中的元素类型为任意类型
const array: any[] = [1, "a", true];

// !void
// void 是表示没有任意类型，就是什么类型都不是，在定义函数，函数没有返回值时会用到
// void 类型的变量只能赋值为 undefined 和 null，其他类型不能赋值给 void 类型的变量
const consoleText = (text: string): void => {
  // return text //不能将类型'string'分配给类型'void'
  console.log(text);
};

// !never
// never 类型指那些永不存在的值的类型，它是那些总会抛出异常或根本不会有返回值的函数表达式的返回值类型，当变量被永不为真的类型保护所约束时，该变量也是 never 类型。

// 这个 errorFunc 函数总是会抛出异常，所以它的返回值类型是 never，用来表明它的返回值是永不存在的。
const errorFunc = (message: string): never => {
  throw new Error(message);
};

// infiniteFunc也是根本不会有返回值的函数，infiniteFunc是死循环是根本不会返回值的
const infiniteFunc = (): never => {
  while (true) {}
};

// never 类型是任何类型的子类型，所以它可以赋值给任何类型；而没有类型是 never 的子类型，所以除了它自身没有任何类型可以赋值给 never 类型，any 类型也不能赋值给 never 类型
// let neverVariable = (() => {
//   while (true) { }
// })();
// neverVariable = 123; // error 不能将类型"number"分配给类型"never"

// !unknown
// unknown相对于any是安全的
// 我们知道当一个值我们不能确定它的类型的时候，可以指定它是any类型；但是当指定了any类型之后，这个值基本上是“废”了，你可以随意对它进行属性方法的访问，不管有的还是没有的，可以把它当做任意类型的值来使用，这往往会产生问题

// !交叉类型
// 交叉类型就是取多个类型的并集，使用 & 符号定义，被&符链接的多个类型构成一个交叉类型，表示这个类型同时具备这几个连接起来的类型
const merge = <T, U>(arg1: T, arg2: U): T & U => {
  let res = <T & U>{}; // 这里指定返回值的类型兼备T和U两个类型变量代表的类型的特点
  // res = Object.assign(arg1, arg2); //不能将类型“{} & U”分配给类型“T & U”。
  return res;
};
const info1 = {
  name: "lison",
};
const info2 = {
  age: 18,
};
const lisonInfo = merge(info1, info2);
// console.log(lisonInfo.address); // error 类型“{ name: string; } & { age: number; }”上不存在属性“address”

// !联合类型
// 联合类型是要求只要符合联合类型中任意一种类型即可，它使用 | 符号定义。当我们的程序具有多样性，元素类型不唯一时，即使用联合类型
const getLength = (content: string | number): number => {
  if (typeof content === "string") return content.length;
  else return content.toString().length;
};
console.log(getLength("abc")); // 3
console.log(getLength(123)); // 3
